<template xmlns:v-slot="http://www.w3.org/1999/XSL/Transform">
  <div>
    <el-form
      ref="form"
      class="app-container"
      :model="form"
      :size="size"
      :label-width="labelWidth || '200px'"
    >
      <form-item :inputs="inputList" :operation="operation" :form="form">
        <template v-slot:string="{ input }">
          <el-input
            v-model="form[input.field]"
            :placeholder="input.placeholder"
            :disabled="input.traits.includes('disabled')"
            @change="handleInputChange(form[input.field], input)"
          />
        </template>
        <template v-slot:int="{ input }">
          <el-input-number
            v-model="form[input.field]"
            :placeholder="input.placeholder"
            :disabled="input.traits.includes('disabled')"
            :min="input | optionsFilter('min')"
            :max="input | optionsFilter('max')"
            :step="input | optionsFilter('step')"
            @change="handleInputChange(form[input.field], input)"
          />
        </template>
        <template v-slot:float="{ input }">
          <el-input-number
            v-model="form[input.field]"
            :placeholder="input.placeholder"
            :disabled="input.traits.includes('disabled')"
            :min="input | optionsFilter('min')"
            :max="input | optionsFilter('max')"
            :step="input | optionsFilter('step')"
            @change="handleInputChange(form[input.field], input)"
          />
        </template>
        <template v-slot:slider="{ input }">
          <el-slider
            :ref="'input_' + input.field"
            v-model="form[input.field]"
            :placeholder="input.placeholder"
            :disabled="input.traits.includes('disabled')"
            show-input
            :min="input | optionsFilter('min')"
            :max="input | optionsFilter('max')"
            :step="input | optionsFilter('step')"
            @change="handleInputChange(form[input.field], input)"
          />
        </template>
        <template v-slot:select="{ input }">
          <el-select
            v-model="form[input.field]"
            :placeholder="input.placeholder"
            collapse-tags
            :disabled="input.traits.includes('disabled')"
            :multiple="input.traits.includes('multiple')"
            @change="handleInputChange(form[input.field], input)"
          >
            <el-option
              v-for="(option) in input.options"
              :key="option.value"
              :label="option.label"
              :value="option.value"
              :disabled="option.disabled"
            />
          </el-select>
        </template>
        <template v-slot:cascade="{ input }">
          <el-cascader
            v-model="form[input.field]"
            :placeholder="input.placeholder"
            collapse-tags
            :disabled="input.traits.includes('disabled')"
            :clearable="input.traits.includes('clearable')"
            :filterable="input.traits.includes('filterable')"
            :show-all-levels="input.traits.includes('last')"
            :options="input.options"
            :props="{ multiple: input.traits.includes('multiple') }"
            @change="handleInputChange(form[input.field], input)"
          />
        </template>
        <template v-slot:datetime="{ input }">
          <el-col>
            <el-date-picker
              v-model="form[input.field]"
              :placeholder="input.placeholder"
              :disabled="input.traits.includes('disabled')"
              type="datetime"
              @change="handleInputChange(form[input.field], input)"
            />
          </el-col>
        </template>
        <template v-slot:timestamp="{ input }">
          <el-col>
            <el-date-picker
              v-model="form[input.field]"
              :placeholder="input.placeholder"
              :disabled="input.traits.includes('disabled')"
              type="datetime"
              @change="handleInputChange(form[input.field], input)"
            />
          </el-col>
        </template>
        <template v-slot:switch="{ input }">
          <el-switch
            v-model="form[input.field]"
            :placeholder="input.placeholder"
            :disabled="input.traits.includes('disabled')"
            @change="handleInputChange(form[input.field], input)"
          />
        </template>
        <template v-slot:radio="{ input }">
          <el-radio-group
            v-model="form[input.field]"
            :placeholder="input.placeholder"
            :disabled="input.traits.includes('disabled')"
            @change="handleInputChange(form[input.field], input)"
          >
            <el-radio
              v-for="(option) in input.options"
              :key="option.value"
              :label="option.value"
              :value="option.value"
              :disabled="input.traits.includes('disabled')"
            >
              {{ option.label }}
            </el-radio>
          </el-radio-group>
        </template>
        <template v-slot:textarea="{ input }">
          <el-input
            v-model="form[input.field]"
            :placeholder="input.placeholder"
            :disabled="input.traits.includes('disabled')"
            type="textarea"
            @change="handleInputChange(form[input.field], input)"
          />
        </template>
        <template v-slot:markdown="{ input }">
          <markdown-editor
            v-model="form[input.field]"
            :placeholder="input.placeholder"
            :disabled="input.traits.includes('disabled')"
            :options="getMarkdownOptions(input.options)"
            :height="getMarkdownOptions(input.options).index || '200px'"
            @change="handleInputChange(form[input.field], input)"
          />
        </template>
        <template v-slot:file="{ input }">
          <el-upload
            :ref="'upload_' + input.field"
            :action="input | uploadFileUrlFilter(uploadFileUrl, baseUrl)"
            :data="submitForm"
            :on-success="handleUploadSuccess"
            :before-remove="handleBeforeRemove"
            :on-remove="handleRemove"
            :auto-upload="!isMultiple(input)"
            :limit="isMultiple(input) ? 300 : 1"
            :multiple="isMultiple(input)"
            :name="input.field"
            :on-exceed="handleExceed"
            :file-list="fileMap[input.field]"
            :disabled="input.traits.includes('disabled')"
          >
            <el-button size="small" type="primary">选择文件</el-button>
          </el-upload>
          <el-button
            v-if="isMultiple(input)"
            style="margin-left: 10px;"
            type="success"
            @click="handleSubmitUpload(input)"
          >上传到服务器
          </el-button>
        </template>
        <template v-slot:image="{ input }">
          <el-upload
            :ref="'upload_' + input.field"
            :action="input | uploadFileUrlFilter(uploadImageUrl, baseUrl)"
            :data="submitForm"
            list-type="picture-card"
            :on-preview="handlePictureCardPreview"
            :on-success="handleUploadSuccess"
            :before-remove="handleBeforeRemove"
            :on-remove="handleRemove"
            :auto-upload="!isMultiple(input)"
            :limit="isMultiple(input) ? 300 : 1"
            :multiple="isMultiple(input)"
            :name="input.field"
            :on-exceed="handleExceed"
            :file-list="fileMap[input.field]"
            :disabled="input.traits.includes('disabled')"
          >
            <i class="el-icon-plus" @click="handleUploadClick($event, input)" />
          </el-upload>
          <el-button
            v-if="isMultiple(input)"
            style="margin-left: 10px;"
            type="success"
            @click="handleSubmitUpload(input)"
          >上传到服务器
          </el-button>
        </template>
      </form-item>
    </el-form>
    <slot name="form-btn">
      <div class="form-footer">
        <el-button @click="onCancel">取消</el-button>
        <el-button type="primary" @click="onSubmit">提交</el-button>
      </div>
    </slot>
    <el-dialog style="text-align: center;" tabindex="99" :visible.sync="dialogVisible" append-to-body>
      <img style="max-height: 100%;max-width: 100%" :src="dialogImageUrl" alt="">
    </el-dialog>
  </div>
</template>

<script>
/* eslint-disable vue/require-prop-types */
import { mapGetters } from 'vuex'
import FormItem from './form-item'
import { parseTime, basename, formatOptions, jsonParse } from '@/utils'
import OperationChangeHandler from '@/layout/mixin/OperationChangeHandler'
import MarkdownEditor from '@/components/MarkdownEditor'

export default {
  name: 'ComplexForm',
  components: { FormItem, MarkdownEditor },
  filters: {
    // 上传地址过滤 如果operation不配置 则使用默认请求地址
    uploadFileUrlFilter(input, uploadFileUrl, baseUrl) {
      let uploadUrl = uploadFileUrl
      input.operations.forEach(operation => {
        if (operation.action === 'upload' && operation.url) {
          uploadUrl = baseUrl + operation.url
        }
      })
      return uploadUrl
    },
    // [slider] 选项过滤
    // option.label为选项类型 option.value 为值
    optionsFilter(input, type) {
      let result
      input.options.forEach(option => {
        if (option.label === type) {
          result = option.value
        }
      })
      return result
    }
  },
  mixins: [OperationChangeHandler],
  props: ['inputs', 'operation', 'formId', 'labelWidth', 'info', 'size'],
  data() {
    return {
      // 表单绑定model
      form: {},
      // 文件列表
      fileMap: {},
      // inputs 解除绑定成 inputList
      inputList: [],
      // 图片弹窗地址
      dialogImageUrl: '',
      // 图片弹窗是否显示
      dialogVisible: false
    }
  },
  computed: {
    ...mapGetters([
      'baseUrl'
    ]),
    // 默认的上传地址
    uploadImageUrl() {
      return this.baseUrl + '/adm/upload/image'
    },
    uploadFileUrl() {
      return this.baseUrl + '/adm/upload/file'
    },
    // 最终提交的表单数据
    submitForm() {
      return this.getSubmitForm()
    }
  },
  watch: {
    inputs() {
      this.init()
    }
  },
  created() {
    this.init()
  },
  methods: {
    // 初始化
    init() {
      // 1.整理传进来的inputs数据 通过这种方式解除外部绑定关系
      this.inputList = this.inputs.map(item => {
        let input = Object.assign({}, item) // 解除外部绑定关系
        if (input.value === undefined || input.value === null) {
          input.value = input.default
        }

        switch (input.type) {
          case 'timestamp':
            input.value = parseTime(input.value, '{y}-{m}-{d} {h}:{i}:{s}')
            break

          case 'file':
          case 'image':
            if (input.value && input.value !== '') {
              // 把初始文件丢个fileMap进行对应显示
              this.fileMap[input.field] = [{
                name: basename(input.value),
                url: input.value,
                field: input.field
              }]
            } else {
              this.fileMap[input.field] = []
            }
            break
          case 'images':
          case 'files':
            // 把初始文件丢个fileMap进行对应显示
            if (input.value && input.value !== '') {
              // 有值
              let values = []
              if (input.value instanceof Array) {
                values = input.value
              } else {
                values = jsonParse(input.value)
              }
              this.fileMap[input.field] = values.map(value => {
                return {
                  name: basename(value),
                  url: value,
                  field: input.field
                }
              })
              input.value = values
            } else {
              this.fileMap[input.field] = []
              input.value = []
            }

            // 转变成 multiple image 或 multiple file
            input.traits = [...input.traits, 'multiple']
            input.type = input.type.substring(0, input.type.length - 1)
            break

          case 'select':
          case 'radio':
            input = this.formatOptions(input)
            break

          case 'switch':
            // 把值临时存起来 与true和false 相对应
            input.valueTemp = []
            input.options.map(option => {
              input.valueTemp.push({
                status: option.label === 'true',
                value: option.value
              })
            })

            // 找到值对应的状态
            // eslint-disable-next-line no-case-declarations
            const find = input.valueTemp.find(item => {
              return item.value === input.value
            })
            input.value = find && find.status
            break

          case 'slider':
            // 滑块需要转换成数字
            input.value = Number(input.value)
            input.options.forEach(option => {
              option.value = Number(option.value)
            })
            break

          case 'cascade':
            input.options = formatOptions(input.options)

            break

          default:
            break
        }

        // 多项选择/文件 值改成数组
        if (input.value && this.isMultiple(input) && !(input.value instanceof Array)) {
          if (typeof input.value === 'string') {
            input.value = input.value === '' ? [] : input.value.split(',')
          } else {
            input.value = [input.value]
          }
        }

        return input
      })

      this.inputList = this.inputList.filter(item => {
        if (this.formId <= 0) {
          // 创建时隐藏
          if ((item.showStatus & 2) !== 2) {
            return false
          }
        } else if ((item.showStatus & 4) !== 4) {
          // 编辑时隐藏
          return false
        }
        return true
      })

      // 2. 数据丢给form
      this.form = this.inputList.reduce((previous, current) => {
        previous[current.field] = current.value
        return previous
      }, {})

      // 附加的文件数据
      this.form._FILES = {}

      // 3. 外部可以监听创建完后的数据
      this.$emit('created', this.getSubmitForm(), this.operation, this.formId, this.info)

      // 4. 逐一处理input的operationChange操作
      this.inputList.map(input => {
        this.handleInputInit(input.value, input)
      })
    },
    // 提交
    onSubmit() {
      let isSuccess = true
      this.$refs['form'].validate((valid, validateError) => {
        if (valid) {
          this.$emit('submit', this.getSubmitForm(), this.operation, this.formId, this.info)
        } else {
          this.$emit('submit-error', validateError, this.operation, this.formId, this.info)
          isSuccess = false
        }
      })
      return isSuccess
    },
    // 取消提交
    onCancel() {
      this.$emit('cancel', this.operation, this.formId, this.info)
    },
    // 上传成功后执行
    handleUploadSuccess(response, file, files) {
      if (response.code > 0 && response.data) {
        if (!response.data.field) {
          this.$message.warning(`上传接口没有返回field字段`)
        }

        const field = response.data.field
        // 把接口下发的数据放在FILES字段传回接口
        if (!this.form._FILES[field]) {
          this.form._FILES[field] = []
        }
        this.form._FILES[field].push(response.data)

        // 把已上传的文件存到fileMap
        this.fileMap[field] = files.map((item) => {
          return {
            name: item.name,
            uid: item.uid,
            status: item.status,
            url: item.response ? item.response.data.url : item.url
          }
        })

        // 如果是上传多个则用数组返回多个, 否则用字符串返回单个
        if (this.form[field] instanceof Array) {
          this.form[field] = this.fileMap[field].map(item => {
            return item.url
          })
        } else {
          this.form[field] = response.data.url
        }
      } else {
        file.status = 'error'
        this.$message.error('上传失败')
      }
    },
    // 点击上传到服务器按钮执行 -> 找到对应的上传组件调用其submit()方法进行上传
    handleSubmitUpload(input) {
      this.$refs['upload_' + input.field].submit()
    },
    // 预览图片
    handlePictureCardPreview(file) {
      this.dialogImageUrl = file.url
      this.dialogVisible = true
    },
    // 点击上传区域 检查是否失效和超出文件限制
    handleUploadClick($event, input) {
      if (input.traits.includes('disabled')) {
        this.$message.warning(`此上传按钮处于失效状态`)
        $event.stopPropagation()
      }
      if (!this.isMultiple(input) && this.fileMap[input.field].length >= 1) {
        // 单个文件 并且已经有文件存在 提示先删除
        this.handleExceed()
        $event.stopPropagation()
      }
    },
    // 超出文件限制
    handleExceed(file, files) {
      this.$message.warning(`文件数超出限制，请先删减`)
    },
    // 文件删除弹窗
    handleBeforeRemove(file, files) {
      return this.$confirm(`确定移除 ${file.name}？`)
    },
    // 文件删除
    handleRemove(file, files) {
      for (const field in this.fileMap) {
        // 处理临时文件fileMap的数据
        this.fileMap[field] = this.fileMap[field].filter((item) => {
          const remain = file.uid !== item.uid
          if (!remain && this.form._FILES[field]) {
            // 处理_FILES里的数据
            this.form._FILES[field] = this.form._FILES[field].filter(file => {
              return file.url !== item.url
            })
          }
          return remain
        })

        // 处理表单的数据
        if (this.form[field] instanceof Array) {
          // 多文件
          this.form[field] = this.fileMap[field].map(item => {
            return item.url
          })
        } else {
          // 单文件
          this.form[field] = this.fileMap[field][0] ? this.fileMap[field][0].url : ''
        }
      }
    },
    // 是否为多选
    isMultiple(input) {
      return input.traits.includes('multiple')
    },
    // 把对象options转换成数组options
    formatOptions(input) {
      input.options = formatOptions(input.options)
      return input
    },
    // 把输入的值格式化为提交值
    getSubmitForm() {
      const submitForm = {}
      for (let i = 0; i < this.inputList.length; i++) {
        if (this.form[this.inputList[i].field] !== undefined && this.form[this.inputList[i].field] !== null) {
          // // 忽略 在编辑和新增隐藏的字段
          // if (this.inputList[i].showStatus) {
          //   if (((this.inputList[i].showStatus & 4) !== 4) && ((this.inputList[i].showStatus & 2) !== 2)) {
          //     continue
          //   }
          // }

          switch (this.inputList[i].type) {
            case 'timestamp':
              submitForm[this.inputList[i].field] = (new Date(this.form[this.inputList[i].field])).getTime() / 1000
              break
            case 'datetime':
              submitForm[this.inputList[i].field] = parseTime(this.form[this.inputList[i].field], '{y}-{m}-{d} {h}:{i}:{s}')
              break

            case 'switch':
              // eslint-disable-next-line no-case-declarations
              const find = this.inputList[i].valueTemp.find(item => {
                return item.status === this.form[this.inputList[i].field]
              })
              if (find) {
                submitForm[this.inputList[i].field] = find.value
              } else {
                submitForm[this.inputList[i].field] = this.form[this.inputList[i].field]
              }
              break

            default:
              submitForm[this.inputList[i].field] = this.form[this.inputList[i].field]
              break
          }
        }
      }

      if (Object.keys(this.form._FILES).length > 0) {
        submitForm['_FILES'] = JSON.stringify(this.form._FILES)
      }
      return submitForm
    },
    // 表单初始化完执行
    handleInputInit(value, input) {
      this.handleInputChange(value, input, true)
    },
    // 表单被修改后执行 -> 查看是否有传 operation 有则根据 operation.action来执行对应的Handler方法
    handleInputChange(value, input, isInit = false) {
      if (!isInit) {
        this.$emit('input-change', value, input)
      }
      if (input.operations.length === 0) {
        return false
      }
      input.operations.map(operation => {
        const handler = operation.action + 'Handler'
        if (typeof this[handler] === 'function') {
          this[handler]({ operation, input, value, isInit })
        }
      })
    },
    getMarkdownOptions(options) {
      const newOptions = {}
      options.forEach(option => {
        newOptions[option.label] = option.value
      })
      return newOptions
    }
  }
}
</script>

<style scoped>
</style>

